import re

import pyrem_torq.expression as _pte
from pyrem_torq.extra.expression_shortname import A, M, N, NM, BtN, X, S

from pyrem_torq.extra import NodeWithOrigin
from nodeformatter import *
import _prepscript_util as _pu

class MacroValue(object):
    def __init__(self, name):
        self.name = name
    def __repr__(self):
        return "MacroValue(%s)" % self.name

MacroValueDefined = MacroValue("Defined") # this means the name is just defiend without value, like "#define NDEBUG"
MacroValueUndefed = MacroValue("Undefed") # this means the name is un-defed.
MacroValueNegative = MacroValue("Negative") # this means #if name -> #if false, #if defined name -> #if false

class MacroDefinitionError(Exception): pass

class MacroInterpreter(object):
    def __init__(self, getMacroCode=False):
        self.optionGetMacroCode = getMacroCode
        self.__patName = re.compile("[a-zA-Z_][a-zA-Z0-9_]*")
        self.exprs = []
        self.variableTable = dict()
        self.replaceTable = dict() # str -> str
        self.build_exprs()
        
    def build_exprs(self):
        comp = _pu.expr_compile
        def comp_gsub(s, replaces=None): return S(comp(s, replaces=replaces))
        assign_marker_expr = _pte.assign_marker_expr
        def replaces_from_locals(ld): return dict(( k, v ) for k, v in ld.items() if isinstance(v, _pte.TorqExpression))

        exprs = []
        
        eolExpr = comp(r'("\r" | "\n" | "\r\n");')
        wordLikeExpr = comp(r'ri"^[a-z_]";')
        extractLiteralExpr = comp(r"""(
            (l_string <- ?"L", "\"", *("\\", any | any^("\"" | @eolExpr)), "\"")
            | (l_char <- ?"L", "'", *("\\", any | any^("'" | @eolExpr)), "'")
            | (l_int <- ri"^0x[a-f0-9]+$", req^(i"p"), ?ri"^[ul]+$")
            | (l_float <- ri"^0x[a-f0-9.]+$", ?(i"p", ?("-" | "+"), ?r"^\d"), ?i"l")
            | (l_int <- r"^\d+$", req^(i"e" | i"f"), ?ri"^[ul]+$")
            | (l_float <- r"^[0-9][0-9.]+$", ?(i"e", ?("-" | "+"), r"^\d"), ?ri"^[fl]+$")
        );""", replaces=replaces_from_locals(locals()))
        
        rSExpr = comp(r'(null <- +(r"[ \t]" | space));')
        rIfSExpr = comp(r'(null <- *(r"[ \t]" | space));')
        mExprExpr = comp(r"""
        +(
            @rIfSExpr, (
                (M_OP_DEFINED <- "defined") 
                | (id <- @wordLikeExpr) 
                | (op_logical_and <- "&", "&") | (op_logical_or <- "|", "|")
                | (op_le <- "<", "=") | (op_ge <- ">", "=") | (op_eq <- "=", "=") | (op_ne <- "!", "=")
                | (op_lshift <- "<", "<") | (op_rshift <- ">", ">")
                | (M_OP_CONCAT <- "#", "#") | (M_OP_TOSTRING <- "#")
                | (op_star <- "*") # may mean mul or indirection
                | (op_div <- "/")
                | (op_mod <- "%")
                | (op_plus <- "+") # may mean add or sign plus
                | (op_minus <- "-") # may mean sub or sign minus
                | (op_amp <- "&") # may mean bitwise and or indirection
                | (op_logical_neg <- "!")
                | (op_complement <- "~") | (op_or <- "|") | (op_xor <- "^")
                | (OL <- "<") # may mean less than or template parameter
                | (OG <- ">") # may mean greater than or template parameter
                | (ques <- "?") | (colon <- ":")
                | @extractLiteralExpr 
                | (LP <- "(") | (RP <- ")")
            )
        );
        """, replaces=replaces_from_locals(locals()))
    
        exprs.append(comp_gsub(r"""
        (configure_macro_line <- (<>macro_line :: (
            (M_DEFINE <- "#", @rIfSExpr, "define"), @rSExpr, (id <- @wordLikeExpr), req^("("), ?(@rSExpr, @mExprExpr), @rIfSExpr
            | (M_DEFINE_WITH_ARGUMENT <- "#", @rIfSExpr, "define"), @rSExpr, (id <- @wordLikeExpr), req("("), ?@mExprExpr, @rIfSExpr
            | (M_UNDEF <- "#", @rIfSExpr, "undef"), @rSExpr, (id <- @wordLikeExpr), @rIfSExpr
            | (M_IF <- "#", @rIfSExpr, "ifdef"), (M_OP_DEFINED <-), @rSExpr, (id <- @wordLikeExpr), @rIfSExpr
            | (M_IF <- "#", @rIfSExpr, "ifndef"), (op_logical_neg <-), (M_OP_DEFINED <-), @rSExpr, (id <- @wordLikeExpr), @rIfSExpr
            | (M_IF <- "#", @rIfSExpr, "if"), @rSExpr, @mExprExpr, @rIfSExpr
            | (M_ELIF <- "#", @rIfSExpr, "elif"), @rSExpr, @mExprExpr, @rIfSExpr
            | (M_ELSE <- "#", @rIfSExpr, "else"), @rIfSExpr
            | (M_ENDIF <- "#", @rIfSExpr, "endif"), @rIfSExpr
        )))
        | (null <- macro_line | multiline_comment | singleline_comment | r"[ \t]" | "\f" | "\\", *r"[ \t]", @eolExpr | @eolExpr);
        """, replaces=replaces_from_locals(locals())))
        
        exprExpr = comp("""
            (M_EXPR <- M_OP_DEFINED, (null<- LP), *@0, (null <- RP)) 
            | (M_EXPR <- M_OP_DEFINED, @0) 
            | (M_EXPR <- (null <- LP), *@0, (null <- RP)) | req^(LP|RP), any;
        """)
        exprs.append(( 'paren expr', comp_gsub('configure_macro_line :: (M_IF|M_ELIF), +@exprExpr;', 
            replaces=replaces_from_locals(locals())) ))
        
        def macro_concatinatable_binop(opName):
            tExpr = X(N(opName)) + (NM('M_EXPR', M("0")) | A())
            termExpr = BtN('M_EXPR', [2,]*tExpr) | tExpr
            expr = (termExpr + [1,]*(N(opName) + termExpr)) | [1,]*tExpr
            assign_marker_expr(expr, "0", expr) # make it recursive expr
            return comp_gsub('configure_macro_line :: (M_IF|M_ELIF), @expr;', replaces=replaces_from_locals(locals()))
        def macro_binop(opNames):
            assert opNames # len(opNames) >= 1
            oneOfOperatorsExpr = N(opNames[0])
            for opName in opNames[1:]: oneOfOperatorsExpr = oneOfOperatorsExpr | N(opName)
                                   
            tExpr = (X(oneOfOperatorsExpr) + (NM('M_EXPR', M("expr")) | A()))
            termExpr = BtN('M_EXPR', [2,]*tExpr) | tExpr
            expr = (termExpr + oneOfOperatorsExpr + M("0")) | [1,]*tExpr
            assign_marker_expr(expr, "0", expr) # right recursive
            assign_marker_expr(tExpr, "expr", expr) # make it recursive expr
            return comp_gsub('configure_macro_line :: (M_IF|M_ELIF), @expr;', replaces=replaces_from_locals(locals()))
        exprs.append(( 'macro-or expr', macro_concatinatable_binop('op_logical_or') ))
        exprs.append(( 'macro-and expr', macro_concatinatable_binop('op_logical_and') ))
        exprs.append(( 'macro-eq/ne expr', macro_binop(( 'op_eq', 'op_ne' )) ))
        exprs.append(( 'macro-cmp expr', macro_binop(( 'OL', 'OG', 'op_le', 'op_ge' )) ))
        
        exprs.append(comp_gsub("""
        (configure_macro_line :: M_IF, *any), (M_CLAUSE <- *(req^(configure_macro_line :: (M_ELSE | M_ELIF), *any), @0)),
                *((configure_macro_line :: M_ELIF, *any), (M_CLAUSE <- *(req^(configure_macro_line :: (M_ELSE | M_ELIF), *any), @0))),
                ?((configure_macro_line :: M_ELSE, *any), (M_CLAUSE <- *(req^(configure_macro_line :: (M_ELSE | M_ELIF), *any), @0))),
                (configure_macro_line :: M_ENDIF, *any)
        | (configure_macro_line :: (M_DEFINE|M_DEFINE_WITH_ARGUMENT), *any)
        | req^(configure_macro_line), any;
        """))
        
        self.exprs = exprs
        
    def parse_configure_macro_line(self, seq):
        for expr in self.exprs:
            if isinstance(expr, tuple):
                #stepLabel = expr[0]
                expr = expr[1]
            posDelta, outSeq, dropSeq = expr.match(seq, 1)
            assert 1 + posDelta == len(seq)
            seq = [ seq[0] ] + outSeq
        return seq
    
    @staticmethod
    def __get_label(item):
        if isinstance(item, list) and item: return item[0]
        return None
    @staticmethod
    def __split_configure_macro_line(item):
        if MacroInterpreter.__get_label(item) == 'configure_macro_line':
            return item[1][0], item[2:]
        else:
            return None, None
    
    def evaluate_expression(self, expr):
        get_label = MacroInterpreter.__get_label
        len_expr = len(expr)
    
        label = get_label(expr[1])
        if label == 'M_OP_DEFINED':
            if len_expr != 3: return None # invalid expr
            if expr[2][0] != 'id': return None # invalid expr
            id = "".join(expr[2][1:])
            v = self.variableTable.get(id, None)
            return None if v is None else \
                    0 if v is MacroValueUndefed or v is MacroValueNegative else \
                    1
        elif label == 'op_logical_neg':
            rExpr = [ 'expr' ] + expr[2:]
            value = self.evaluate_expression(rExpr)
            return None if value is None else \
                    not value
        elif label == 'id':
            id = "".join(expr[1][1:])
            v = self.variableTable.get(id, None)
            return 0 if v is MacroValueUndefed or v is MacroValueNegative else \
                    None if v is MacroValueDefined else \
                    v
        else:
            if expr[0] in ( 'expr', 'M_EXPR' ):
                value = self.evaluate_expression(expr[1])
                i = 2
                while i + 1 < len_expr:
                    op = get_label(expr[i])
                    right = self.evaluate_expression(expr[i + 1])
                    if op == 'op_logical_or':
                        value = True if value or right else None if value is None or right is None else False
                    elif op == 'op_logical_and':
                        value = False if not value or not right else None if value is None or right is None else True
                    elif op == 'op_ne':
                        value = None if value is None or right is None else value != right
                    elif op == 'op_eq':
                        value = None if value is None or right is None else value == right
                    elif op == 'op_le':
                        value = None if value is None or right is None else value <= right
                    elif op == 'op_ge':
                        value = None if value is None or right is None else value >= right
                    elif op == 'OG':
                        value = None if value is None or right is None else value > right
                    elif op == 'OL':
                        value = None if value is None or right is None else value < right
                    else:
                        return None # invalid expr
                    i += 2
                if i != len_expr:
                    return None # invalid expr
                return value
            elif expr[0].startswith("l_"):
                if expr[0] == 'l_int':
                    s = "".join(expr[1:])
                    try:
                        if s[0:2] in ( "0x", "0X" ): return int(s[2:], 16)
                        elif s[0:1] == "0": return int(s, 8)
                        else: return int(s)
                    except ValueError:
                        return None # invalid expr
                else:
                    return None # unknown value
            else:
                return None # unknown value/invalid expr
    
    def interpret(self, seq):
        get_label = MacroInterpreter.__get_label
        split_configure_macro_line = MacroInterpreter.__split_configure_macro_line
        
        seq = seq + [ None ] # sentinel
        modifiedSeq = [ seq[0] ]
        len_seq = len(seq)
        prevI = None
        i = 1
        while i < len_seq:
            assert i != prevI
            prevI = i
            cmd, expr = split_configure_macro_line(seq[i])
            if cmd in ('M_IF', 'M_ELIF'):
                value = self.evaluate_expression([ 'expr' ] + expr)
                i += 1
                if value is None: # unknown value
                    pass
                elif value not in ( 0, False ):
                    item = seq[i]
                    if get_label(item) == 'M_CLAUSE':
                        m2 = self.interpret(item); modifiedSeq.extend(m2[1:])
                        i += 1
                        while i + 1 <= len_seq and get_label(split_configure_macro_line(seq[i])) == 'M_ELIF' and get_label(seq[i + 1]) == 'M_CLAUSE':
                            i += 2
                        cmd, _ = split_configure_macro_line(seq[i])
                        if cmd == 'M_ELSE':
                            if get_label(seq[i + 1]) == 'M_CLAUSE': 
                                i += 2
                            cmd, _ = split_configure_macro_line(seq[i])
                        if cmd == 'M_ENDIF':
                            i += 1
                else:
                    item = seq[i]
                    if get_label(item) == 'M_CLAUSE':
                        i += 1
                    cmd, _ = split_configure_macro_line(seq[i])
                    if cmd == 'M_ELSE':
                        if i + 1 < len_seq and get_label(seq[i + 1]) == 'M_CLAUSE':
                            m2 = self.interpret(seq[i + 1]); modifiedSeq.extend(m2[1:])
                            i += 2
                        cmd, _ = split_configure_macro_line(seq[i])
                    if cmd == 'M_ENDIF':
                        i += 1
            elif cmd:
                i += 1
            else:
                lbl = get_label(seq[i])
                if lbl == 'M_CLAUSE':
                    m2 = self.interpret(seq[i]); modifiedSeq.extend(m2[1:])
                else:
                    modifiedSeq.append(seq[i])
                i += 1
        assert modifiedSeq[-1] == None # sentinel
        modifiedSeq = modifiedSeq[:-1]
        return modifiedSeq
    
    def replace(self, seq):
        replacedSeq = [ seq[0] ]
        fset = frozenset(".eEpPfF")
        for i, item in enumerate(seq[1:]):
            if isinstance(item, list) and item[0] == 'word':
                itemValueString = "".join(item[1:])
                v = self.replaceTable.get(itemValueString, None)
                if v is not None:
                    if not v: # empty string
                        pass
                    else:
                        ref = ( item[1], item[-1] )
                        v0 = v[0]
                        if '0' <= v0 <= '9' or v0 == '.':
                            name = 'l_float' if fset.intersection(v) else 'l_int'
                        elif v0 == '"':
                            name = 'l_string'
                        elif v0 == "'":
                            name = 'l_int'
                        else:
                            name = item[0]
                        replacedItem = NodeWithOrigin([ name + v ], item)
                        replacedSeq.append(replacedItem)
                else:
                    replacedSeq.append(item)
            else:
                replacedSeq.append(item)
        return replacedSeq
    
    def __call__(self, seq):
        seq = self.parse_configure_macro_line(seq)
        if not self.optionGetMacroCode:
            seq = self.interpret(seq)
            seq = self.replace(seq)
        return seq
    
    def build_tables(self, definitions):
        variableTable = dict()
        replaceTable = dict()
        for d in definitions:
            if d.startswith("-D"):
                dstr = d[2:]
                p = dstr.find("=")
                if p >= 0: name, value = dstr[:p], dstr[p + 1:]
                else: name, value = dstr, ""
                variableTable[name] = value if value else MacroValueDefined
                if not value or self.__patName.match(value):
                    replaceTable[name] = value
            elif d.startswith("-U"):
                name = d[2:]
                variableTable[name] = MacroValueUndefed
            elif d.startswith("-N"):
                name = d[2:]
                variableTable[name] = MacroValueNegative
                replaceTable[name] = "" # empty string
            else:
                raise MacroDefinitionError
        assert '' not in variableTable
        assert '' not in replaceTable
        self.variableTable = variableTable
        self.replaceTable = replaceTable

